{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "COVID-Train&Validation.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/mr7495/COVID-CT-Code/blob/master/COVID_Train%26Validation.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "o-J5B4axhabZ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#Code from https://github.com/mr7495/COVID-CT-Code\n",
        "#developer email :mr7495@yahoo.com"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6JmhSAzXPUaX",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "!nvidia-smi #show the allocated GPU"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "5yqhPi-FS60r",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#Connect your Google Drive \n",
        "from google.colab import drive\n",
        "drive.mount('/content/drive')"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7UAp6XlqPeou",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#Install essential libraries\n",
        "!pip install zipfile36\n",
        "!pip install git+https://github.com/mr7495/RetinaNet\n",
        "#Downgrade Tensorflow and Keras because updated versions may cause some errors\n",
        "#!pip uninstall tensorflow\n",
        "#!pip uninstall keras\n",
        "#!pip install tensorflow==2.2\n",
        "#!pip install keras==2.3.0\n"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "zw0BhQ7nPihU",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "import keras\n",
        "import numpy as np\n",
        "import cv2\n",
        "import os\n",
        "import random\n",
        "import shutil\n",
        "import pandas as pd\n",
        "import csv\n",
        "import zipfile\n",
        "from keras import optimizers\n",
        "from keras.models import Sequential,Model\n",
        "from keras.layers import Dropout, Flatten, Dense,Input\n",
        "from keras.applications.resnet_v2 import ResNet50V2\n",
        "from keras.applications.xception import Xception\n",
        "from keras.applications.resnet50 import ResNet50\n",
        "from keras.applications.vgg16 import VGG16\n",
        "from keras.callbacks import ModelCheckpoint\n",
        "from keras.applications.imagenet_utils import preprocess_input\n",
        "from keras import backend as K\n",
        "from keras.preprocessing.image import ImageDataGenerator\n",
        "from keras.initializers import RandomNormal\n",
        "import keras.backend as k\n",
        "from sklearn.utils import shuffle\n",
        "import io\n",
        "from PIL import Image as pil_image\n",
        "from keras_retinanet import layers\n",
        "import keras.backend as k\n",
        "import keras_retinanet"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "aCUZApPBTyF0",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# Add the COVID-CTset to your drive through this link:\n",
        "#https://drive.google.com/drive/folders/1xdk-mCkxCDNwsMAk2SGv203rY1mrbnPB?usp=sharing"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "nhzh5DCPgYhQ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "archive = zipfile.ZipFile('drive/My Drive/COVID-CTset/Train&Validation.zip') #Path to the shared data for training and validation\n",
        "for file in archive.namelist():\n",
        "     archive.extract(file, './data') #Extract the data"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "JzN_Dxg5V14k",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "fold_num=1 #Select Fold Number"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tpjAiyH3bKIu",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#Here we set the data generators for applying data augmentation methods\n",
        "train_datagen = ImageDataGenerator(horizontal_flip=True,vertical_flip=True,zoom_range=0.05,rotation_range=360,width_shift_range=0.05,height_shift_range=0.05,shear_range=0.05)\n",
        "test_datagen = ImageDataGenerator()\n",
        "train_df =pd.read_csv('drive/My Drive/COVID-CTset/CSV/train{}.csv'.format(fold_num)) #raed train csv file\n",
        "validation_df = pd.read_csv('drive/My Drive/COVID-CTset/CSV/validation{}.csv'.format(fold_num)) #raed validation csv file (Validation in the training process)\n",
        "train_df = shuffle(train_df) #Shuffle the train data\n",
        "test_df = pd.read_csv('drive/My Drive/COVID-CTset/CSV/test{}.csv'.format(fold_num))#raed test csv file (For evaluating the final version of the trained network)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "bITv5T0wVT-f",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "shape=(512,512,1) #shape of the dataset images (in TIFF format)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "FvqnEK7hRf4M",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#Create the generators\n",
        "train_generator = train_datagen.flow_from_dataframe(\n",
        "      dataframe=train_df,\n",
        "      directory='data',\n",
        "      x_col=\"filename\",\n",
        "      y_col=\"class\",\n",
        "      target_size=shape[:2],\n",
        "      batch_size=14,\n",
        "      class_mode='categorical',color_mode=\"grayscale\",shuffle=True)\n",
        "validation_generator = test_datagen.flow_from_dataframe(\n",
        "        dataframe=validation_df,\n",
        "        directory='data',\n",
        "        x_col=\"filename\",\n",
        "        y_col=\"class\",\n",
        "        target_size=shape[:2],\n",
        "        batch_size=10,\n",
        "        class_mode='categorical',color_mode=\"grayscale\",shuffle=True)\n",
        "test_generator = test_datagen.flow_from_dataframe(\n",
        "        dataframe=test_df,\n",
        "        directory='data',\n",
        "        x_col=\"filename\",\n",
        "        y_col=\"class\",\n",
        "        target_size=shape[:2],\n",
        "        batch_size=10,\n",
        "        class_mode='categorical',color_mode=\"grayscale\",shuffle=True)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "lhGTOcC-QZ1o",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "k.clear_session() #Clear keras backend \n",
        "try:\n",
        "  os.mkdir('models') #create folder for saving the trained networks\n",
        "except:\n",
        "  pass\n",
        "full_name='ResNet50V2-FPN-fold{}'.format(fold_num)\n",
        "classes_number=2 #Number of classes (normal and COVID-19)\n",
        "input_tensor=Input(shape=shape)\n",
        "weight_model = ResNet50V2(weights='imagenet', include_top=False) #Load ResNet50V2 ImageNet pre-trained weights\n",
        "weight_model.save_weights('weights.h5') #Save the weights\n",
        "base_model = ResNet50V2(weights=None, include_top=False, input_tensor=input_tensor) #Load the ResNet50V2 model without weights\n",
        "base_model.load_weights('weights.h5',skip_mismatch=True, by_name=True) #Load the ImageNet weights on the ResNet50V2 model except the first layer(because the first layer has one channel in our case)\n",
        "\n",
        "#Create Feature Pyramid Network (FPN)\n",
        "# We used some help for writing the Pyramid from the written code on https://github.com/fizyr/keras-retinanet/blob/master/keras_retinanet/models/retinanet.py\n",
        "feature_size=256 #Set the feature channels of the FPN\n",
        "layer_names = [\"conv4_block1_preact_relu\", \"conv5_block1_preact_relu\", \"post_relu\"] #Layers of ResNet50V2 with different scale features \n",
        "layer_outputs = [base_model.get_layer(name).output for name in layer_names]\n",
        "C3, C4, C5=layer_outputs #Features of different scales, extracted from ResNet50V2\n",
        "P5           = keras.layers.Conv2D(feature_size, kernel_size=1, strides=1, padding='same', name='C5_reduced')(C5)\n",
        "P5_upsampled = layers.UpsampleLike(name='P5_upsampled')([P5, C4])\n",
        "P5           = keras.layers.Conv2D(feature_size, kernel_size=3, strides=1, padding='same', name='P5')(P5)\n",
        "\n",
        "# Concatenate P5 elementwise to C4\n",
        "P4           = keras.layers.Conv2D(feature_size, kernel_size=1, strides=1, padding='same', name='C4_reduced')(C4)\n",
        "P4           = keras.layers.Concatenate(axis=3)([P5_upsampled, P4])\n",
        "P4_upsampled = layers.UpsampleLike(name='P4_upsampled')([P4, C3])\n",
        "P4           = keras.layers.Conv2D(feature_size, kernel_size=3, strides=1, name='P4')(P4)\n",
        "\n",
        "# Concatenate P4 elementwise to C3\n",
        "P3 = keras.layers.Conv2D(feature_size, kernel_size=1, strides=1, padding='same', name='C3_reduced')(C3)\n",
        "P3 = keras.layers.Concatenate(axis=3)([P4_upsampled, P3])\n",
        "P3 = keras.layers.Conv2D(feature_size, kernel_size=3, strides=1, name='P3')(P3)\n",
        "\n",
        "# \"P6 is obtained via a 3x3 stride-2 conv on C5\"\n",
        "P6 = keras.layers.Conv2D(feature_size, kernel_size=3, strides=2, padding='same', name='P6')(C5)\n",
        "\n",
        "# \"P7 is computed by applying ReLU followed by a 3x3 stride-2 conv on P6\"\n",
        "P7 = keras.layers.Activation('relu', name='C6_relu')(P6)\n",
        "P7 = keras.layers.Conv2D(feature_size, kernel_size=3, strides=2, padding='same', name='P7')(P7)\n",
        "\n",
        "# Run classification for each of the generated features from the pyramid\n",
        "feature1 = Flatten()(P3)\n",
        "dp1 = Dropout(0.5)(feature1)\n",
        "preds1 = Dense(2, activation='relu',kernel_initializer=RandomNormal(mean=0.0, stddev=0.001))(dp1)\n",
        "#################################################################\n",
        "feature2 = Flatten()(P4)\n",
        "dp2 = Dropout(0.5)(feature2)\n",
        "preds2 = Dense(2, activation='relu',kernel_initializer=RandomNormal(mean=0.0, stddev=0.001))(dp2)\n",
        "#################################################################\n",
        "feature3 = Flatten()(P5)\n",
        "dp3= Dropout(0.5)(feature3)\n",
        "preds3 = Dense(2, activation='relu',kernel_initializer=RandomNormal(mean=0.0, stddev=0.001))(dp3)\n",
        "#################################################################\n",
        "feature4 = Flatten()(P6)\n",
        "dp4 = Dropout(0.5)(feature4)\n",
        "preds4 = Dense(2, activation='relu',kernel_initializer=RandomNormal(mean=0.0, stddev=0.001))(dp4)\n",
        "#################################################################\n",
        "feature5 = Flatten()(P7)\n",
        "dp5 = Dropout(0.5)(feature5)\n",
        "preds5 = Dense(2, activation='relu',kernel_initializer=RandomNormal(mean=0.0, stddev=0.001))(dp5)\n",
        "#################################################################\n",
        "concat=keras.layers.Concatenate(axis=1)([preds1,preds2,preds3,preds4,preds5]) #Concatenate the predictions(Classification results) of each of the pyramid features \n",
        "out=keras.layers.Dense(2,activation='softmax',kernel_initializer=RandomNormal(mean=0.0, stddev=0.001))(concat) #Final Classification\n",
        "\n",
        "model = Model(inputs=base_model.input, outputs=out) #Create the Training Model\n",
        "#######################################################\n",
        "for layer in model.layers:\n",
        "  layer.trainable = True\n",
        "model.compile(optimizer=optimizers.Nadam(lr=0.0001), loss='categorical_crossentropy',metrics=['accuracy'])\n",
        "filepath=\"models/%s-{epoch:02d}-{val_accuracy:.4f}.hdf5\"%full_name  # Path to save the trained models\n",
        "checkpoint = ModelCheckpoint(filepath, monitor='val_accuracy', save_best_only=True, mode='max') #creating checkpoint to save the best validation accuracy\n",
        "callbacks_list = [checkpoint]\n",
        "\n",
        "model.fit_generator(train_generator, epochs=20,validation_data=validation_generator,shuffle=True,callbacks=callbacks_list) #start training"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_xE1h0BqQZ7C",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#Model Evaluation\n",
        "trained_models=[]\n",
        "for r,d,f in os.walk('models'): #Take the path to the trained nets \n",
        "  for file in f:\n",
        "    if '.hdf5' in file:\n",
        "      trained_models.append(os.path.join(r,file))\n",
        "\n",
        "reports=[]\n",
        "for trn_model in trained_models: #evaluate the network on each trained net\n",
        "  k.clear_session()\n",
        "  custom_object={'UpsampleLike': keras_retinanet.layers._misc.UpsampleLike}\n",
        "  netpath=trn_model \n",
        "  model_name=trn_model\n",
        "  fold_num=trn_model[trn_model.index('fold')+4] #find the fold number\n",
        "  net=keras.models.load_model(netpath, custom_objects=custom_object) #load model\n",
        "\n",
        "  covid_label= test_generator.class_indices['covid'] #get the index of COVID-19 class \n",
        "  normal_label= test_generator.class_indices['normal']  #get the index of normal class \n",
        "  tp=0 #True Positives\n",
        "  fp=0 #False Positives\n",
        "  anum=0 #All the images numbers\n",
        "  ###########\n",
        "  ctp=0 #Correct classified COVID-19 cases\n",
        "  cfp=0 #Wrong classified COVID-19 cases\n",
        "  cfn=0 #Not classified COVID-19 cases\n",
        "  ctn=0 #Correctly not classified COVID-19 cases\n",
        "  cnum=0 #Number of COVID cases\n",
        "  ################\n",
        "  ntp=0 #Correct classified normal cases\n",
        "  nfp=0 #Wrong classified normal cases\n",
        "  nfn=0 #Not classified normal cases\n",
        "  ntn=0 #Correctly not classified normal cases\n",
        "  nnum=0 #Number of normal cases\n",
        "  for num,img_name in enumerate(test_generator.filenames): #load image\n",
        "    gt_ind=test_generator.classes[num] #get the loaded image class index\n",
        "    img=cv2.imread(os.path.join('data',img_name),cv2.IMREAD_UNCHANGED) #load image\n",
        "    pred_ind=np.argmax(net.predict(np.expand_dims(np.expand_dims(img,axis=0),axis=3))[0]) #get the predicted class index\n",
        "    anum+=1 #count the number of images\n",
        "    if gt_ind==covid_label:\n",
        "      cnum+=1\n",
        "      if pred_ind==covid_label:\n",
        "        tp+=1\n",
        "        ctp+=1\n",
        "        ntn+=1\n",
        "      else:\n",
        "        fp+=1\n",
        "        nfp+=1\n",
        "        cfn+=1\n",
        "    elif gt_ind==normal_label:\n",
        "      nnum+=1\n",
        "      if pred_ind==normal_label:\n",
        "        ctn+=1\n",
        "        ntp+=1\n",
        "        tp+=1\n",
        "      else:\n",
        "        cfp+=1\n",
        "        nfn+=1\n",
        "        fp+=1\n",
        "\n",
        "  overall_acc=tp/(tp+fp) #overall accuracy\n",
        "  cacc=(ctp+ctn)/(ctp+ctn+cfp+cfn) #covid accurayc\n",
        "  nacc=(ntp+ntn)/(ntp+ntn+nfp+nfn) #normal accuracy\n",
        "  csens=ctp/(ctp+cfn) #covid sensitivity\n",
        "  nsens=ntp/(ntp+nfn) #normal sensitivity\n",
        "  cspec=ctn/(ctn+cfp) #covid specificity\n",
        "  nspec=ntn/(ntn+nfp) #normal specificity\n",
        "  cprec=ctp/(ctp+cfp) #covid precision\n",
        "  nprec=ntp/(ntp+nfp) #normal precision\n",
        "\n",
        "  reports.append([model_name,fold_num,tp,fp,ctp,cfn,cfp,ntp,nfn,nfp,overall_acc,cacc,nacc,csens,nsens,cspec,nspec,cprec,nprec])\n",
        "\n",
        "\n",
        "  print(model_name)\n",
        "  print('tp: ',tp,'fp: ',fp)\n",
        "\n",
        "with open('FPN.csv', mode='w',newline='') as csv_file:\n",
        "    csvwriter = csv.writer(csv_file, delimiter=',', quotechar='\"',quoting=csv.QUOTE_MINIMAL)\n",
        "    csvwriter.writerow(['model_name','fold_num','tp','fp','ctp','cfn','cfp','ntp','nfn','nfp','overall_acc','cacc','nacc','csens','nsens','cspec','nspec','cprec','nprec'])\n",
        "    for row in reports:\n",
        "        csvwriter.writerow(row)  "
      ],
      "execution_count": 0,
      "outputs": []
    }
  ]
}
